package com.heartunderblade.rpc.utils;

import cn.hutool.core.io.resource.ResourceUtil;
import com.heartunderblade.rpc.service.Serializer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SpiLoader {
    /**
     * 存储已经记载的类,一个serializer 对应一个Map集合，Map集合包含例如 jdk - jdkSerializer
     */
    public static final ConcurrentHashMap<String, Map<String, Class<?>>> loaderMap = new ConcurrentHashMap<>();
    /**
     * 对象实例缓存，避免重复new，类路径 -> 对象实例，单例？
     */
    public static final Map<String, Object> instanceCache = new ConcurrentHashMap<>();
    /**
     * 系统 SPI 目录
     */
    private static final String RPC_SYSTEM_SPI_DIR = "META-INF/rpc/system/";

    /**
     * 用户自定义 SPI 目录
     */
    private static final String RPC_CUSTOM_SPI_DIR = "META-INF/rpc/custom/";
    /**
     * 扫描路径
     */
    private static final String[] SCAN_DIRS = new String[]{RPC_SYSTEM_SPI_DIR, RPC_CUSTOM_SPI_DIR};
    /**
     * 动态加载的类列表[interface com.heartunderblade.rpc.service.Serializer]
     */
    private static final List<Class<?>> LOAD_CLASS_LIST = Arrays.asList(Serializer.class);

    /**
     * 加载所有的SPI
     */
    public static void loadAll() {
        System.out.println("加载所有的SPI");
        for (Class<?> aClass : LOAD_CLASS_LIST) {
            load(aClass);
        }
    }


    /**
     * 加载写在配置文件下的类放入keyClassMap,再放入到loaderMap存储加载的类名和对应的keyClassMap
     *
     * @param loadClass
     * @return
     */
    public static Map<String, Class<?>> load(Class<?> loadClass) {

        System.out.println("加载类型为 " + loadClass.getName() + " 的SPI");
        //规定用户定义的SPI 优先级高于 系统SPI
        HashMap<String, Class<?>> keyClassMap = new HashMap<>();
        //遍历custom和system目录
        for (String scanDir : SCAN_DIRS) {
            List<URL> resources = ResourceUtil.getResources(scanDir + loadClass.getName());
            //得到文件的全路径/META-INF/rpc/system/com.heartunderblade.rpc.service.Serializer
            //得到文件的全路径/META-INF/rpc/custom/com.heartunderblade.rpc.service.Serializer
            //读取每个资源文件
            for (URL resource : resources) {
                try {
                    InputStreamReader inputStreamReader = new InputStreamReader(resource.openStream());
                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        //遍历每一行，用=分开 ， jdk=com.heartunderblade.rpc.service.impl.JdkSerializer
                        String[] strArray = line.split("=");
                        //例如jdk
                        String key = strArray[0];
                        //例如com.heartunderblade.rpc.service.impl.JdkSerializer
                        String className = strArray[1];
                        //放入结果的map
                        keyClassMap.put(key, Class.forName(className));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("spi resource load error");
                    throw new RuntimeException(e);
                }
            }
        }
            loaderMap.put(loadClass.getName(), keyClassMap);
        return keyClassMap;
    }

    public static <T> T getInstance(Class<?> tClass, String key) {
        //得到com.heartunderblade.rpc.service.Serializer
        String tClassName = tClass.getName();
        //从loaderMap获取对应的实现类
        Map<String, Class<?>> keyClassMap = loaderMap.get(tClassName);
        if(keyClassMap == null){
            throw new RuntimeException(String.format("SpiLoader 未加载 %s 类型", tClassName));
        }
        //不包含key的情况
        if(!keyClassMap.containsKey(key)){
            throw new RuntimeException(String.format("SpiLoader 的 %s 不存在 key=%s 的类型", tClassName, key));
        }
        //获得要加载的类型例如jdk
        Class<?> implClass = keyClassMap.get(key);
        // 从实例缓存中加载指定类型的实例
        String implClassName  = implClass.getName();
       //先判断缓存已经是否有实例
        if(!instanceCache.containsKey(implClassName)){
            try {
                //没有直接put
                instanceCache.put(implClassName,implClass.newInstance());
            } catch (Exception e) {
                String errorMsg = String.format("%s 类实例化失败", implClassName);
                throw new RuntimeException(errorMsg, e);
            }
        }
        //缓存里没有的话，前面的步骤也会加入到缓存，有的话直接get
        return (T)instanceCache.get(implClassName);
    }


    public static void main(String[] args) throws IOException, ClassNotFoundException {
        loadAll();
        System.out.println(loaderMap);
        Serializer serializer = getInstance(Serializer.class, "kryo");
        System.out.println(serializer);
    }


}
